'''
Created on May 11, 2010

@author: roni
'''
import subprocess
import logging
import os
import time
from abnormalScenarioGenerator import AbnormalScenarioGenerator
from traceParser import TraceParser
from ns2Scenario import Ns2Scenario
from gde import GDE
from projectConstants import ProjectConstants
from diagnosers.linearProgrammingGde import LinearProgrammingGde
from generators.environmentGenerator import EnvironmentGenerator
from diagnosers.minimalCardinalityGde import MinimalCardinalityGde
from common.utils import TimeoutFunction
from common.utils import TimeoutFunctionException


class ExperimentRunner(object):
    '''
    Runs an experiment on a given scenario and abnormality. Simulates the abnormal scenario and diagnoses the problem
    '''
    
    def __init__(self):
        self.trace_parser = TraceParser()
        self.abnormality_generator = AbnormalScenarioGenerator()
        self.diagnoser = GDE()
        self.lp_diagnoser = LinearProgrammingGde()
        self.classic_diagnoser = GDE(False)
        self.gde_min_card_diagnoser = MinimalCardinalityGde()
       
    def run(self,scenario, node_to_abnormal_delay):
        ''' The full loop: Create abnormal scenario, run simulation and diagnose it. '''         
        flow_to_abnormal_details = self.run_abnormal_simulation(scenario, node_to_abnormal_delay, abnormal_scenario_file="abnormal.scenario")
        self.lp_diagnose(scenario, flow_to_abnormal_details, node_to_abnormal_delay)
        return self.gde_diagnose(scenario, flow_to_abnormal_details, node_to_abnormal_delay)
    
    def diagnose(self,diagnoser,scenario, flow_to_abnormal_details,node_to_abnormal_delay, verify=False,output_diagnosises=False):
        ''' Diagnose '''
        
        logging.info("Running diagnosis with diagnoser %s..." % diagnoser.name)
                
        max_time=120
        time_out = False
        timed_func = TimeoutFunction(diagnoser.run, max_time)
        try:
            start = time.time()
#            timed_func(** {'a':1, 'b':2})
#            timed_func(** {'scenario':scenario, 'flow_to_abnormal_details':flow_to_abnormal_details})
            results = timed_func(* (scenario, flow_to_abnormal_details))
            total_runtime = time.time()-start
            results = ["Ok"]
        except TimeoutFunctionException: 
            logging.info("Timeout!")       
            time_out = True
            results = ["Timeout"]

        if time_out==False:
            abnormals = set(node_to_abnormal_delay.keys())
            
            # Analyze results
            results.append(len(diagnoser.diagnosises))
            results.append(total_runtime)
            
            min_cardinality = min([len(diagnoser.diagnosises[i]) for i in xrange(len(diagnoser.diagnosises))])
            results.append(min_cardinality)
            results.append(len(diagnoser.diagnosises)>diagnoser.max_diagnoses)
            if output_diagnosises:
                for i in xrange(len(diagnoser.diagnosises)):
                    diagnosis = diagnoser.diagnosises[i]
                    values=dict()
                    if isinstance(diagnosis,dict):
                        values = diagnosis
                        diagnosis = set(diagnosis.keys())
                    else:
                        for abnormal in diagnosis:
                            values[abnormal]=EnvironmentGenerator.ABNORMAL_ADDED_DELAY                    
                    runtime = diagnoser.runtimes[i]
                    false_positive = diagnosis.difference(abnormals)            
                    results.append(len(false_positive))
                    if len(false_positive)>0:
                        average_diff = sum([values[item] for item in false_positive])/len(false_positive)
                    else:
                        average_diff = 0
                    results.append(average_diff)
                    
                    
                    false_negative = abnormals.difference(diagnosis)
                    results.append(len(false_negative))
                    if len(false_negative)>0:
                        average_diff = sum([EnvironmentGenerator.ABNORMAL_ADDED_DELAY for item in false_negative])/len(false_negative)
                    else:
                        average_diff = 0
                    results.append(average_diff)
                    
                    true_positive  = abnormals.intersection(diagnosis)
                    results.append(len(true_positive))
                    results.append(runtime)
                
            if verify:
                self.verify_diagnosis(scenario, flow_to_abnormal_details, diagnoser.diagnosises)
                        
        return results        
        
    def gde_diagnose(self,scenario, flow_to_abnormal_details, node_to_abnormal_delay):
        ''' Diagnose given abnormal details with classic GDE approach '''
        return self.diagnose(self.diagnoser, scenario, flow_to_abnormal_details, node_to_abnormal_delay)

    def classic_gde_diagnose(self,scenario, flow_to_abnormal_details, node_to_abnormal_delay):
        ''' Diagnose given abnormal details with classic GDE approach '''
        return self.diagnose(self.classic_diagnoser, scenario, flow_to_abnormal_details, node_to_abnormal_delay)

    def gde_min_card_diagnose(self,scenario, flow_to_abnormal_details, node_to_abnormal_delay):
        ''' Diagnose given abnormal details with classic GDE approach '''
        return self.diagnose(self.gde_min_card_diagnoser, scenario, flow_to_abnormal_details, node_to_abnormal_delay)
    
    def lp_diagnose(self,scenario, flow_to_abnormal_details, node_to_abnormal_delay):
        ''' Diagnose given abnormal details with linear programming approach '''
        logging.info("Run linear programming diagnoser...")
        return  self.diagnose(self.lp_diagnoser, scenario, flow_to_abnormal_details, node_to_abnormal_delay)
#        self.verify_diagnosis(scenario, flow_to_abnormal_details, self.lp_diagnoser.diagnosises)
    
            
    
    def verify_diagnosis(self,scenario, observed_flow_to_details,diagnosises):
        ''' Verifies that a diagnosis is correct, by running a simulation and comparing resulting delays '''
        for diagnosis in diagnosises:
            # Run abnormal scenario according to discovered diagnosis
            diagnosed_node_to_delay = dict()            
            for abnormal in diagnosis:
                diagnosed_node_to_delay[abnormal] = Ns2Scenario.LINK_DELAY*1000
            self.verify_single_diagnosis(scenario, observed_flow_to_details, diagnosed_node_to_delay,accuracy_margin=0.05)
        
        return True;
    
    def verify_single_diagnosis(self,scenario,observed_flow_to_details, diagnosis,accuracy_margin=0.005):
        ''' Check if the diagnosis produces the same results are the observed data '''
        flow_to_details = self.run_abnormal_simulation(scenario, diagnosis,abnormal_scenario_file="diagnosis.scenario")
        if observed_flow_to_details != flow_to_details:
            for flow_id in observed_flow_to_details:
                if observed_flow_to_details[flow_id].__eq__(flow_to_details[flow_id])==False:
                    error = observed_flow_to_details[flow_id].delay-flow_to_details[flow_id].delay
                    if abs(error)>accuracy_margin:
                        raise ValueError("Diagnosis %s does not match flow details: delays:%s,%s" % (diagnosis.keys(),observed_flow_to_details[flow_id].delay,flow_to_details[flow_id].delay))
                    else:
                        logging.info("Diagnosis missed by %3.4f" % error)
                    
        logging.info("Verified diagnosis %s" % diagnosis)                
            
    def run_abnormal_simulation(self,scenario, node_to_delay, abnormal_scenario_file="temp.scenario",\
                                abnormal_trace_file = "temp.abnormal.tr"):
        ''' Create an abnormal scenario and run, and return the resulting flow_to_details '''
        # Modify original scenario                 
        abnormal_scenario_full_path = "%s/%s" % (ProjectConstants.RESROUCE_DIR,abnormal_scenario_file)
        self.abnormality_generator.generateScenario(scenario, abnormal_scenario_full_path, node_to_delay)        

        # Run simulation on abnormal scenario to create trace
        abnormal_scenario = Ns2Scenario(abnormal_scenario_full_path, simulate=False)
        return abnormal_scenario.simulate()
    
                


        

